<div id='title'><span style='font-size: 3em; font-family: fantasy; text-shadow: 3px 3px 3px silver;'>why don&apos;t you drop your image-file here ?</span></div>
<script type='text/javascript'>

var DEBUG = {
	viewer : null,
	init : function() {
		var div = document.createElement('DIV');
		with (div.style) {
			marginLeft = '50%';
			height = '89%';
			overflowY = 'scroll';
			color = 'white';
			backgroundColor = 'black';
		}
		document.body.appendChild(div);
		this.viewer = div;
	},
	text : function(in_text) {
		if (!this.viewer) {
			return;
		}
		var div = document.createElement('DIV');
		div.textContent = in_text;
		this.viewer.insertBefore(div, this.viewer.firstChild);
	},
	obj : function(in_obj) {
		if (!this.viewer) {
			return;
		}
		for (var prop in in_obj) {
			switch (typeof in_obj[prop]) {
			case 'string' :
			case 'number' :
				this.text(prop + ' : ' + in_obj[prop]);
				break;
			default :
				break;
			}
		}
	}
};

var CONFIG = {
	MAXX : 4,
	MAXY : 4,
	COMPLETE : function() {
		alert('congratulations');
	},
	/* event-handler should not be property of gMap, because "this" means window in event-handler. */
	ONCLICK : function(in_ev) {
		if ((typeof in_ev.offsetX) != 'undefined') {
			gMap.select(in_ev.offsetX, in_ev.offsetY);
		} else {
			gMap.select(in_ev.layerX, in_ev.layerY);
		}
	},
	IMGTOP : '10px',
	IMGLEFT : '10px'
};

function px2num(in_px)
{
	return Number(in_px.split('px')[0]);
}

var gMap = {
	_w : CONFIG.MAXX,
	_h : CONFIG.MAXY,
	/* set after registerImg() */
	_pszw : -1,
	_pszh : -1,
	/* set after registerImg() */
	_blank : {
		x : -1,
		y : -1
	},
	/* set after registerImg() */
	_img : null,
	/* set after registerImg() */
	_canvas : null,
	/*
		e.g.
		0 --> (0, 0)
		1 --> (1, 0)
		2 --> (2, 0)
		3 --> (3, 0)
		4 --> (0, 1)
		5 --> (1, 1)
		6 --> (2, 1)
		7 --> (3, 1)
		...
	*/
	_data : [],
	_inAnimationProgress : false,
	_xy2rect : function(in_x, in_y) {
		return {
			x1 : this._pszw * in_x,
			y1 : this._pszh * in_y,
			x2 : this._pszw * (in_x + 1) - 1,
			y2 : this._pszh * (in_y + 1) - 1,
			w  : this._pszw,
			h  : this._pszh
		};
	},
	_n2xy : function(in_n) {
		var x = in_n % this._w;
		return {x : x, y : (in_n - x) / this._w};
	},
	_xy2n : function(in_x, in_y) {
		return in_y * this._w + in_x;
	},
	_isBlank : function(in_x, in_y) {
		if (this._blank.x == in_x) {
			if (this._blank.y == in_y) {
				return true;
			}
		}
		return false;
	},
	_setBlank : function(in_x, in_y) {
		this._blank.x = in_x;
		this._blank.y = in_y;
	},
	_getBlank : function() {
		return {x : this._blank.x, y : this._blank.y};
	},
	_valid : function(in_x, in_y) {
		if ((0 <= in_x) && (in_x < this._w) && (0 <= in_y) && (in_y < this._h)) {
			return true;
		} else {
			return false;
		}
	},
	_arround : function(in_x, in_y) {
		var ret = [];
		var tmp = [
			{x : in_x - 1, y : in_y},
			{x : in_x, y : in_y - 1},
			{x : in_x + 1, y : in_y},
			{x : in_x, y : in_y + 1}
		];
		for (var i = 0; i < tmp.length; i++) {
			if (this._valid(tmp[i].x, tmp[i].y)) {
				ret.push({x : tmp[i].x, y : tmp[i].y});
			}
		}
		return ret;
	},
	_select : function(in_x, in_y) {
		DEBUG.text('click');
		var tmp = this._arround(in_x, in_y);
		for (var i = 0; i < tmp.length; i++) {
			if (this._isBlank(tmp[i].x, tmp[i].y)) {
				this._swap(in_x, in_y, true);
				if (this._isComplete()) {
					CONFIG.COMPLETE();
				}
				break;
			}
		}
	},
	_swap : function(in_x, in_y, in_animation) {
		/* you can't use 'this' in callback. */
		var self = this;
		var blank = this._getBlank();
		var n1 = self._xy2n(in_x, in_y);
		var n2 = self._xy2n(blank.x, blank.y);
		var v1 = self._data[n1];
		var v2 = self._data[n2];
		var callback = (function() {return function() {
			/* swap */
			self._data[n1] = v2;
			self._data[n2] = v1;
			self._setBlank(in_x, in_y);
			DEBUG.text(blank.x + ',' + blank.y + '(' + v2 + ') ---> ' + v1);
			self._drawPieceOnData(n2, false);
			self._inAnimationProgress = false;
		};})();
		if (!in_animation) {
			callback();
			return;
		}
		var animation = {frame : 7, spf : 20, mag : []};
		for (var i = 0; i < animation.frame; i++) {
			animation.mag[i] = -1 / animation.frame / 2 * i * (i - animation.frame + 1) + 1;
		}
		/* start rect */
		var s_r = self._xy2rect(in_x, in_y);
		/* end rect */
		var e_r = self._xy2rect(blank.x, blank.y);
		var dx = Math.ceil((e_r.x1 - s_r.x1) / animation.frame);
		var dy = Math.ceil((e_r.y1 - s_r.y1) / animation.frame);
		self._setMiniCanvasOnData(n1);
		var repeat = (function() {return function(in_cnt) {
			if (in_cnt < animation.frame) {
				window.setTimeout(function() {
					/*
						(x2 + mag * w/2, y2 + mag * h/2) = (x1 + w/2 +dx, y1 + h/2 +dy)
							---> (x2, y2) = (x1 + (1 - m) * w/2 +dx, y1 + (1 - m) * h/2 +dy)
					*/
					var mag = animation.mag[in_cnt];
					self._showMiniCanvas(
						Math.ceil(s_r.y1 + (1 - mag) * s_r.w / 2 + dy * in_cnt) + 'px',
						Math.ceil(s_r.x1 + (1 - mag) * s_r.h / 2 + dx * in_cnt) + 'px',
						mag
					);
					if (in_cnt == 0) {
						self._drawPieceOnData(n1, true);
					}
					repeat(in_cnt + 1);
				}, animation.spf);
			} else {
				self._hideMiniCanvas();
				callback();
			}
		};})();
		/*
			(1) self._inAnimationProgress = true
			(2) repeat() ---> repeat() --> ... ---> repeat()
			(3) callback()
			(4) self._inAnimationProgress = false
		*/
		self._inAnimationProgress = true;
		repeat(0);
	},
	_isComplete : function() {
		for (var i = 0; i < this._data.length; i++) {
			if (i != this._data[i]) {
				return false;
			}
		}
		return true;
	},
	_initRand : function() {
		var cnt = 50;
		var blank_before = {x : -1, y : -1};
		while (cnt > 0) {
			var blank = this._getBlank();
			var tmp = this._arround(blank.x, blank.y);
			var pickup = tmp[Math.floor(Math.random() * tmp.length)];
			/* pickup should not be blank_before. */
			if ((blank_before.x == pickup.x) && (blank_before.y == pickup.y)) {
				continue;
			} else {
				cnt--;
				blank_before = blank;
			}
			this._swap(pickup.x, pickup.y, false);
		}
		for (var i = 0; i < this._data.length; i++) {
			this._drawPieceOnData(i, false);
		}
		var blank = this._getBlank();
		this._drawPieceOnData(this._xy2n(blank.x, blank.y), true);
	},
	_rectOnData : function(in_n) {
		var dstn = in_n;
		var dstp = this._n2xy(dstn);
		var dstr = this._xy2rect(dstp.x, dstp.y);
		var srcn = this._data[dstn];
		var srcp = this._n2xy(srcn);
		var srcr = this._xy2rect(srcp.x, srcp.y);
		return {
			dst : dstr,
			src : srcr
		};
	},
	_showMiniCanvas : function(in_top, in_left, in_mag) {
		if (!document.getElementById(this._mini_canvas.id)) {
			document.body.appendChild(this._mini_canvas);
		}
		with (this._mini_canvas.style) {
			top = px2num(CONFIG.IMGTOP) + px2num(in_top) + 'px';
			left = px2num(CONFIG.IMGLEFT) + px2num(in_left) + 'px';
		}
		this._mini_canvas.style.width  = Math.ceil(this._pszw * in_mag) + 'px';
		this._mini_canvas.style.height = Math.ceil(this._pszh * in_mag) + 'px';
		var shadow = Math.ceil(in_mag^4 + 1);
		this._mini_canvas.style.boxShadow = '0px ' + shadow + 'px ' + shadow + 'px black';
	},
	_hideMiniCanvas : function() {
		if (document.getElementById(this._mini_canvas.id)) {
			document.body.removeChild(this._mini_canvas);
		}
	},
	_setMiniCanvasOnData : function(in_n) {
		var r = this._rectOnData(in_n);
		var ctx = this._mini_canvas.getContext('2d');
		ctx.drawImage(this._img, r.src.x1, r.src.y1, r.src.w, r.src.h, 0, 0, r.src.w, r.src.h);
	},
	_drawPieceOnData : function(in_n, in_blank) {
		var r = this._rectOnData(in_n);
		var ctx = this._canvas.getContext('2d');
		if (in_blank) {
			ctx.fillStyle = 'rgba(255, 255, 255, 1)';
			ctx.fillRect(r.dst.x1, r.dst.y1, r.dst.w, r.dst.h);
		} else {
			/* copy image */
			ctx.drawImage(this._img, r.src.x1, r.src.y1, r.src.w, r.src.h, r.dst.x1, r.dst.y1, r.dst.w, r.dst.h);
			/* line */
			var line = function(ctx, x1, y1, x2, y2, color) {
				ctx.beginPath();
				ctx.moveTo(x1, y1);
				ctx.lineTo(x2, y2);
				ctx.strokeStyle = color;
				ctx.stroke();
			};
			line(ctx, r.dst.x1, r.dst.y1, r.dst.x1, r.dst.y2, 'rgba(255, 255, 255, 0.5)');
			line(ctx, r.dst.x1, r.dst.y1, r.dst.x2, r.dst.y1, 'rgba(255, 255, 255, 0.5)');
			line(ctx, r.dst.x2, r.dst.y2, r.dst.x1, r.dst.y2, 'rgba(100, 100, 100, 0.5)');
			line(ctx, r.dst.x2, r.dst.y2, r.dst.x2, r.dst.y1, 'rgba(100, 100, 100, 0.5)');
		}
	},
	/* API */
	registerImg : function(in_elem) {
		with (window.getComputedStyle(in_elem, '')) {
			var szw = px2num(width);
			var szh = px2num(height);
		}
		var resize = {
			szw : szw - (szw % this._w),
			szh : szh - (szh % this._h)
		};
		this._pszw = resize.szw / this._w;
		this._pszh = resize.szh / this._h;
		/* resize IMG */
		in_elem.width = resize.szw;
		in_elem.height = resize.szh;
		/* create CANVAS element */
		var canvas = document.createElement('CANVAS');
		var ctx = canvas.getContext('2d');
		canvas.width = resize.szw;
		canvas.height = resize.szh;
		ctx.drawImage(in_elem, 0, 0, szw, szh, 0, 0, resize.szw, resize.szh);
		document.body.appendChild(canvas);
		canvas.addEventListener('click', CONFIG.ONCLICK, false);
		/* create CANVAS element for animation */
		var mini_canvas = document.createElement('CANVAS');
		mini_canvas.width = this._pszw;
		mini_canvas.height = this._pszh;
		/* overlay */
		var elems = [in_elem, canvas, mini_canvas];
		for (var i = 0; i < elems.length; i++) {
			elems[i].id = 'i' + i;
			with (elems[i].style) {
				position = 'absolute';
				top = CONFIG.IMGTOP;
				left = CONFIG.IMGLEFT;
				zIndex = i;
			}
		}
		/* store */
		this._img = in_elem;
		this._canvas = canvas;
		this._mini_canvas = mini_canvas;
		/* initialize */
		for (var i = 0; i < this._w * this._h; i++) {
			this._data[i] = i;
		}
		this._setBlank(this._w - 1, this._h - 1);
		this._initRand();
	},
	/* API */
	select : function(in_posx, in_posy) {
		if (this._inAnimationProgress) {
			return;
		}
		this._select(
			Math.floor(in_posx / this._pszw),
			Math.floor(in_posy / this._pszh)
		);
	}
};

function dropFile(in_file)
{
	/* DEBUG.init(); */
	document.getElementById('title').style.display = 'none';
	with (new FileReader()) {
		onload = function(in_ev) {
			var img = document.createElement('IMG');
			img.src = in_ev.target.result;
			img.addEventListener('load', function() {
				gMap.registerImg(img);
			}, false);
			document.body.appendChild(img);
		}
		readAsDataURL(in_file);
	}
}

window.addEventListener('dragover', function(in_ev) {
	in_ev.preventDefault();
}, false);

window.addEventListener('drop', function(in_ev) {
	dropFile(in_ev.dataTransfer.files[0]);
	in_ev.preventDefault();
	in_ev.stopPropagation();
}, false);

</script>
